#include "svdpi.h"
#include "common.h"
#include "difftest.h"
#include "itracer.h"
#include "counter.h"

ITracer itracer;
Counter total_inst("instructions");

static const char *regs_name[] = {
    "$0", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
    "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
    "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
    "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"};
extern "C" void difftest_reset()
{
    total_inst = 0;
}
void print_regs(const long long *registers)
{
    for (size_t i = 0; i < 32; i++)
    {
        printf("%-3s 0x%016llx ", regs_name[i], registers[i]);
        if (i % 8 == 7)
        {
            printf("\n");
        }
    }
}

void print_cpu(CPU_state cpu)
{
    printf("PC 0x%08lx\n", cpu.pc);
    for (size_t i = 0; i < 32; i++)
    {
        printf("%-3s 0x%016lx ", regs_name[i], cpu.gpr[i]);
        if (i % 8 == 7)
        {
            printf("\n");
        }
    }
}
#include "mem.h"
#include <queue>
// DPI import at /home/qingchen/emu-sig/mcore/vsrc/difftest/difftest.sv:1:30
CPU_state cpu_state = {.pc = MEMORY_BASE};
std::queue<CPU_state> cpu_checkpoint;
uint64_t lastCommitTime = 0;
void print_cpt()
{
    while (!cpu_checkpoint.empty())
    {
        CPU_state cpt = cpu_checkpoint.front();
        print_cpu(cpt);
        cpu_checkpoint.pop();
    }
    itracer.print();
}
static bool skip = false;
extern "C" void difftest_skip()
{
    skip = true;
}
bool copy_dut = false;
extern "C" void difftest_step(long long pc, int inst, const long long *registers)
{
    static bool hasPrint = false;
    if (hasPrint)
        return;
    itracer.save((ITracer_Data){.instruction = (uint32_t)inst, .pc = (uint64_t)pc});

    cpu_state.pc = pc;
    if (copy_dut)
    {
        ref_difftest_regcpy(&cpu_state, DIFFTEST_TO_REF);
        copy_dut = false;
    }
    else
    {
        Assert(ref_cpu_state.pc == pc, "Difftest : PC check failed");
    }
    memcpy(cpu_state.gpr, registers, sizeof(cpu_state.gpr));
    if (cpu_checkpoint.size() >= 3)
        cpu_checkpoint.pop();
    cpu_checkpoint.push(cpu_state);
    if (skip)
    {
        skip = false;
        copy_dut = true;
    }
    else
    {
        ref_difftest_exec(1);
        ref_difftest_regcpy(&ref_cpu_state, DIFFTEST_TO_DUT);
        for (int i = 0; i < 32; i++)
        {
            Assert(ref_cpu_state.gpr[i] == cpu_state.gpr[i], "Difftest : %s[%d]  check failed, ref 0x%016lx dut 0x%016lx", regs_name[i], i, ref_cpu_state.gpr[i], cpu_state.gpr[i]);
        }
    }

    total_inst++;
    printf("Inst[%ld] PC 0x%08llx\r", (uint64_t)total_inst, pc, inst);
    if (Verilated::gotError())
    {
        printf("\nDut ");
        print_regs(registers);
        printf("Ref\n");
        print_cpu(ref_cpu_state);
        printf("Ref PC 0x%08lx\n", ref_cpu_state.pc);
        printf(COLOR("CPU CheckPoints\n", GREEN));
        print_cpt();
        hasPrint = true;
    }
    lastCommitTime = Verilated::time();
}